/*******************************************************************************
 * Copyright (c) 2007, Geophysical Center RAS
 * Sergey Berezin, Dmitry Voitsekhovskiy
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms,
 * with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * - Neither the name of the Geophysical Center RAS nor the names
 * of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ******************************************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Threading;

namespace VisualEsse.DataEngine
{
    /// <summary>
    /// The EsseParametersCache is an implementation of ICache interface for esse parameters caching
    /// as a collection of special-named binary files.
    /// </summary>
    public sealed class EsseParametersCache : ICache
    {
        private const int SemaphoreSize = 10;
        private string folder;
        private long maxSize;

        private Dictionary<string, Mutex> mutexWriterCollection;

        /// <summary>
        /// Initializes a new instance of the EsseParametersCache class.
        /// </summary>
        /// <param name="folder">Full path to the cache folder.</param>
        /// <param name="maxSize">Maximum size of cache (in bytes).</param>
        public EsseParametersCache(string folder, long maxSize)
        {
            this.folder = folder;
            this.maxSize = maxSize;
            this.mutexWriterCollection = new Dictionary<string, Mutex>(8);

            if (!Directory.Exists(folder))
                Directory.CreateDirectory(folder);    
            
        }

        public static CacheKey CreateKey(EsseParameterDescription parameter, DateTime time)
        {
            string sKey = String.Format("{0}@{1}.asd",
                parameter.EsseParameter.ToString(), 
                time.ToString("yyyyMMddHHmm"));

            return new CacheKey(new object[] { sKey });
        }

        private string GetMutexName(string file)
        {
            return "VEWW." + file.GetHashCode().ToString();
        }


        /// <summary>
        /// Locks for Write
        /// </summary>
        private void LockFile(string file)
        {
            Mutex mutex = new Mutex(false, GetMutexName(file));  
            if (!mutex.WaitOne(10000, false))
                throw new ApplicationException("Required cache file is locked: time-out has expired.");
                       
            mutexWriterCollection.Add(file, mutex);
        }

        /// <summary>
        /// Releases after Write is complete
        /// </summary>
        private void ReleaseFile(string file)
        {
            if (mutexWriterCollection.ContainsKey(file))
            {
                Mutex mutex = mutexWriterCollection[file];
                mutexWriterCollection.Remove(file);
                mutex.ReleaseMutex();                
            }          
        }

        /// <summary>
        /// Checks for Read
        /// </summary>
        private void WaitFile(string file)
        {
            // Waiting for the writing complete (if any)
            Mutex mutex = new Mutex(false, GetMutexName(file));
            if (!mutex.WaitOne(10000, false))
                throw new ApplicationException("Required cache file is locked: time-out has expired.");
            mutex.ReleaseMutex();           
        }

        private void HandleFileMutex(string path, FileAccess access)
        {
            if (access != FileAccess.Read)
                LockFile(path);
            else
                WaitFile(path);
        }

        #region ICache Members

        public void CompressCache()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public System.IO.Stream CreateFile(CacheKey key, System.IO.FileAccess access)
        {
            string file = GetFileName(key, false);
            string path = Path.Combine(folder, file);
            Mutex mutex = null;

            if (!File.Exists(path))
            {
                if (access != FileAccess.Read)
                {
                    mutex = new Mutex(false, GetMutexName(file));
                    if (!mutex.WaitOne(10000, false))
                        throw new ApplicationException("Required cache file is locked: time-out has expired.");                    
                }

                Stream s;
                try
                {
                    s = new CacheStream(this, key, FileMode.CreateNew, access);
                }
                catch
                {
                    mutex.ReleaseMutex();
                    throw;
                }

                if(mutex != null)
                    mutexWriterCollection.Add(file, mutex);
                return s;
            }

            // File EXISTS
            return OpenFile(key, FileMode.Truncate, access);            
        }


        public System.IO.Stream OpenFile(CacheKey key, System.IO.FileAccess access)
        {
            return OpenFile(key, FileMode.Open, access);
        }

        private System.IO.Stream OpenFile(CacheKey key, FileMode mode, System.IO.FileAccess access)
        {
            string file = GetFileName(key, false);
            string path = Path.Combine(folder, file);

            if (!File.Exists(path))
                return null;

            if (access == FileAccess.Read)
            {
                WaitFile(path);
                return new CacheStream(this, key, mode, access);
            }

            // Writer:
            Mutex mutex = new Mutex(false, GetMutexName(file));
            if (!mutex.WaitOne(10000, false))
                throw new ApplicationException("Required cache file is locked: time-out has expired.");

            long totalWait = 0;
            while (totalWait < 10000)
            {
                try
                {
                    Stream s = new CacheStream(this, key, mode, access);
                    mutexWriterCollection.Add(file, mutex);

                    return s;
                }
                catch (FieldAccessException)
                {
                    Thread.Sleep(200);
                    totalWait += 200;
                }
                catch
                {
                    break;
                }
            }

            mutex.ReleaseMutex();
            throw new ApplicationException("Cannot write to the required cache file.");
        }

        public System.IO.Stream OpenOrCreateFile(CacheKey key, System.IO.FileAccess access)
        {
            Stream s = OpenFile(key, access);
            if (s != null)
                return s;

            return CreateFile(key, access);
        }


        public string Folder
        {
            get { return folder; }
        }

        public string GetFileName(CacheKey key, bool updateTime)
        {
            string s = key.Keys[0] as string;
            if (s == null)
                throw new ApplicationException("EsseParametersCache: CacheKey is incorrect");

            s = s.Replace("@", "_at_").Replace(":","_");

            return GetFullPath(s);
        }

        public string GetFullPath(string name)
        {            
            return Path.Combine(folder, name);
        }

        public void UpdateFile(CacheKey key, long newLength)
        {
            string path = GetFullPath(GetFileName(key, false));
            ReleaseFile(path);

            // TODO: CompressCache here!
        }

        public void Dispose()
        {
            foreach (Mutex m in mutexWriterCollection.Values)
            {
                try
                {
                    m.ReleaseMutex();
                }
                catch { }
            }
        }

        #endregion
    }
}
